using System.Collections.Generic; using UnityEngine.UI; namespace UnityEngine.EventSystems { /// <summary> /// Simple event system using physics raycasts. /// </summary> [AddComponentMenu("Event/Physics Raycaster")] [RequireComponent(typeof(Camera))] /// <summary> /// Raycaster for casting against 3D Physics components. /// </summary> public class PhysicsRaycaster : BaseRaycaster { /// <summary> /// Const to use for clarity when no event mask is set /// </summary> protected const int kNoEventMaskSet = -1; protected Camera m_EventCamera; /// <summary> /// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used. /// </summary> [SerializeField] protected LayerMask m_EventMask = kNoEventMaskSet; /// <summary> /// The max number of intersections allowed. 0 = allocating version anything else is non alloc. /// </summary> [SerializeField] protected int m_MaxRayIntersections = 0; protected int m_LastMaxRayIntersections = 0; #if PACKAGE_PHYSICS RaycastHit[] m_Hits; #endif protected PhysicsRaycaster() {} public override Camera eventCamera { get { if (m_EventCamera == null) m_EventCamera = GetComponent<Camera>(); if (m_EventCamera == null) return Camera.main; return m_EventCamera ; } } /// <summary> /// Depth used to determine the order of event processing. /// </summary> public virtual int depth { get { return (eventCamera != null) ? (int)eventCamera.depth : 0xFFFFFF; } } /// <summary> /// Event mask used to determine which objects will receive events. /// </summary> public int finalEventMask { get { return (eventCamera != null) ? eventCamera.cullingMask & m_EventMask : kNoEventMaskSet; } } /// <summary> /// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used. /// </summary> public LayerMask eventMask { get { return m_EventMask; } set { m_EventMask = value; } } /// <summary> /// Max number of ray intersection allowed to be found. /// </summary> /// <remarks> /// A value of zero will represent using the allocating version of the raycast function where as any other value will use the non allocating version. /// </remarks> public int maxRayIntersections { get { return m_MaxRayIntersections; } set { m_MaxRayIntersections = value; } } /// <summary> /// Returns a ray going from camera through the event position and the distance between the near and far clipping planes along that ray. /// </summary> /// <param name="eventData">The pointer event for which we will cast a ray.</param> /// <param name="ray">The ray to use.</param> /// <param name="eventDisplayIndex">The display index used.</param> /// <param name="distanceToClipPlane">The distance between the near and far clipping planes along the ray.</param> /// <returns>True if the operation was successful. false if it was not possible to compute, such as the eventPosition being outside of the view.</returns> protected bool ComputeRayAndDistance(PointerEventData eventData, ref Ray ray, ref int eventDisplayIndex, ref float distanceToClipPlane) { if (eventCamera == null) return false; var eventPosition = MultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position); if (eventPosition != Vector3.zero) { // We support multiple display and display identification based on event position. eventDisplayIndex = (int)eventPosition.z; // Discard events that are not part of this display so the user does not interact with multiple displays at once. if (eventDisplayIndex != eventCamera.targetDisplay) return false; } else { // The multiple display system is not supported on all platforms, when it is not supported the returned position // will be all zeros so when the returned index is 0 we will default to the event data to be safe. eventPosition = eventData.position; } // Cull ray casts that are outside of the view rect. (case 636595) if (!eventCamera.pixelRect.Contains(eventPosition)) return false; ray = eventCamera.ScreenPointToRay(eventPosition); // compensate far plane distance - see MouseEvents.cs float projectionDirection = ray.direction.z; distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection) ? Mathf.Infinity : Mathf.Abs((eventCamera.farClipPlane - eventCamera.nearClipPlane) / projectionDirection); return true; } public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList) { #if PACKAGE_PHYSICS Ray ray = new Ray(); int displayIndex = 0; float distanceToClipPlane = 0; if (!ComputeRayAndDistance(eventData, ref ray, ref displayIndex, ref distanceToClipPlane)) return; int hitCount = 0; if (m_MaxRayIntersections == 0) { if (ReflectionMethodsCache.Singleton.raycast3DAll == null) return; m_Hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, finalEventMask); hitCount = m_Hits.Length; } else { if (ReflectionMethodsCache.Singleton.getRaycastNonAlloc == null) return; if (m_LastMaxRayIntersections != m_MaxRayIntersections) { m_Hits = new RaycastHit[m_MaxRayIntersections]; m_LastMaxRayIntersections = m_MaxRayIntersections; } hitCount = ReflectionMethodsCache.Singleton.getRaycastNonAlloc(ray, m_Hits, distanceToClipPlane, finalEventMask); } if (hitCount != 0) { if (hitCount > 1) System.Array.Sort(m_Hits, 0, hitCount, RaycastHitComparer.instance); for (int b = 0, bmax = hitCount; b < bmax; ++b) { var result = new RaycastResult { gameObject = m_Hits[b].collider.gameObject, module = this, distance = m_Hits[b].distance, worldPosition = m_Hits[b].point, worldNormal = m_Hits[b].normal, screenPosition = eventData.position, displayIndex = displayIndex, index = resultAppendList.Count, sortingLayer = 0, sortingOrder = 0 }; resultAppendList.Add(result); } } #endif } #if PACKAGE_PHYSICS private class RaycastHitComparer : IComparer<RaycastHit> { public static RaycastHitComparer instance = new RaycastHitComparer(); public int Compare(RaycastHit x, RaycastHit y) { return x.distance.CompareTo(y.distance); } } #endif } }